﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace CSharpExt.Net.Proxy
{
    public class UsingProxy
    {
        #region Private Data
        
        private IPEndPoint _proxy;

        #endregion
        
        #region Constructor
        
        public UsingProxy(String host, int port) : this(IPAddress.Parse(host), port)
        {
            
        }

        public UsingProxy(IPAddress host, int port) : this(new IPEndPoint(host, port))
        {

        }

        public UsingProxy(IPEndPoint proxy)
        {
            _proxy = proxy;
        }

        #endregion

        #region Operations

        public TcpClient ConnectTo(String host, int port)
        {
            TcpClient client = new TcpClient();
            client.Connect(_proxy);

            NetworkStream stream = sendConnect(client, host, port);

            String result = WaitForData(client, stream);

            Console.WriteLine(result);

            return client;
        }

        public TcpClient ConnectTo(IPAddress host, int port)
        {
            return ConnectTo(new IPEndPoint(host, port));
        }

        public TcpClient ConnectTo(IPEndPoint remote)
        {
            TcpClient client = new TcpClient();
            client.Connect(_proxy);

            NetworkStream stream = sendConnect(client, remote);

            String result = WaitForData(client, stream);

            Console.WriteLine(result);

            return client;
        }
        
        #endregion
        
        #region Implementation

        private NetworkStream sendConnect(TcpClient client, IPEndPoint remote)
        {
            return sendConnect(client, remote.Address.ToString(), remote.Port);
        }


        private const String CONNECT_80 = "CONNECT {0} HTTP/1.1\r\nHost: {0}\r\n\r\n";
        private const String CONNECT_NN = "CONNECT {0}:{1} HTTP/1.1\r\nHost: {0}\r\n\r\n";

        private NetworkStream sendConnect(TcpClient client, String host, int port)
        {
            String connectCommand;

            if (port == 80)
            {
                connectCommand = String.Format(CONNECT_80, host, port);
            }
            else
            {
                connectCommand = String.Format(CONNECT_NN, host, port);
            }

            byte[] message = Encoding.ASCII.GetBytes(connectCommand);

            NetworkStream stream = client.GetStream();
            stream.Write(message, 0, message.Length);
            stream.Flush();

            return stream;
        }

        private const int WAIT_FOR_DATA_INTERVAL = 50; // 50 ms
        private const int WAIT_FOR_DATA_TIMEOUT = 15000; // 15 seconds

        private String WaitForData(TcpClient client, NetworkStream stream)
        {
            int sleepTime = 0;
            while (!stream.DataAvailable)
            {
                Thread.Sleep(WAIT_FOR_DATA_INTERVAL);
                sleepTime += WAIT_FOR_DATA_INTERVAL;
                if (sleepTime > WAIT_FOR_DATA_TIMEOUT)
                    throw new Exception(String.Format("A timeout while waiting for the proxy server at {0} on port {1} to respond.", _proxy.Address, _proxy.Port));
            }

            StringBuilder response = new StringBuilder();
            byte[] buffer = new byte[client.ReceiveBufferSize];
            int bytes = 0;
            long total = 0;
            do
            {
                bytes = stream.Read(buffer, 0, client.ReceiveBufferSize);
                total += bytes;
                response.Append(ASCIIEncoding.UTF8.GetString(buffer, 0, bytes));
            } while (stream.DataAvailable);

            return response.ToString();
        }
        
        #endregion
    }
}
